package com.soc.auth.config;

import com.soc.auth.enumes.RoleEnum;
import com.soc.auth.filter.JwtAuthenticationTokenFilter;
import com.soc.auth.security.*;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    private final MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    private final MyLogoutSuccessHandler myLogoutSuccessHandler;

    private final MyLoginAuthenticationEntryPoint myLoginAuthenticationEntryPoint;

    private final MyAccessDeniedHandler myAccessDeniedHandler;

    private final MyUserDetailServiceImpl myUserDetailService;

    private final PhoneCaptchaAuthenticationProvider phoneCaptchaAuthenticationProvider;

    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        // 构建全局ProviderManager
        auth.authenticationProvider(phoneCaptchaAuthenticationProvider)
            .authenticationProvider(daoAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //自定义的CustomLoginAuthenticationFilter替换了UsernamePasswordAuthenticationFilter
        http.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            // 配置ExceptionTranslationFilter
            .exceptionHandling()
            // 给ExceptionTranslationFilter添加自定义认证失败异常处理器
            .authenticationEntryPoint(myLoginAuthenticationEntryPoint)
            // 给ExceptionTranslationFilter添加自定义鉴权失败异常处理器
            .accessDeniedHandler(myAccessDeniedHandler)
            .and()
            // 开启 URL 路径拦截，即将 FilterSecurityInterceptor 添加到 SpringSecurity 过滤器链中
            .authorizeRequests()
            .antMatchers("/sendSmsCode","/user/registerStudent", "/file/**" ,"/user/forgetPassword").permitAll()
            .antMatchers("/class/joinClassByInvitationCode", "/user/certification").hasRole(RoleEnum.STUDENT.getMessage())
            .antMatchers("/class/joinClass", "/class/createClass").hasAnyRole(RoleEnum.TEACHER.getMessage(), RoleEnum.ADMIN.getMessage())
            // 除上面外所有请求都需要先进行认证
            .anyRequest().authenticated()
            .and()
            // 配置LogoutFilter
            .logout()
            // 设置LogoutFilter所映射的接口为"/user/logout"，并添加自定义的退出成功处理器
            .logoutUrl("/user/logout").logoutSuccessHandler(myLogoutSuccessHandler)
            .and()
            //使用jwt，不通过Session获取SecurityContext
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            //添加jwtAuthenticationTokenFilter过滤器，并且将jwtAuthenticationTokenFilter放在UsernamePasswordAuthenticationFilter过滤器前
            .and()
            .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
            // 关闭csrf
            .csrf().disable()
            // 开启跨域配置
            .cors();
            // 不需要手动指定 CorsConfigurationSource 对象，SpringSecurity 会从 Spring 容器中找到 CorsConfigurationSource 对象放入
            //.configurationSource(corsConfigurationSource()); // 手动指定 CorsConfigurationSource 对象
    }

    // 设置CorsConfigurationSource，
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        // 所有跨域的配置信息都写在corsConfiguration中
        CorsConfiguration configuration = new CorsConfiguration();
        // 跨域允许时间
        configuration.setMaxAge(3600L);
        // 允许哪个请求来源进行跨域
        configuration.addAllowedOriginPattern("*");
        // 允许哪个方法进行跨域
        configuration.addAllowedMethod("*");
        // 是否允许携带cookie进行跨域
        configuration.setAllowCredentials(true);
        // 允许哪个请求头进行跨域
        configuration.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public MyLoginAuthenticationFilter authenticationFilter() throws Exception {
        MyLoginAuthenticationFilter authenticationFilter = new MyLoginAuthenticationFilter();
        // 给CustomAuthenticationSuccessHandler设置一个局部AuthenticationManager
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());
        // 设置认证成功处理器
        authenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        // 设置认证失败处理器
        authenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        return authenticationFilter;
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(myUserDetailService);
        provider.setPasswordEncoder(passwordEncoder());
        provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常，默认:true-隐藏；false-抛出异常；
        return provider;
    }

    //创建BCryptPasswordEncoder注入容器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(WebSecurity web) {
        // 放行，这种放行不会走 Spring Security过滤器链
        web.ignoring().antMatchers("/sendSmsCode");
    }

}
